# Scala Best Practices от Alexandru Nedelcu

Перевод статьи Alexandru Nedelcu [Scala Best Practices][article].

Ниже приведен список лучших практик, которые [Alexandru Nedelcu](https://github.com/alexandru) составил для своих коллег.

## 0. Предисловие

Советы возникли из болезненного опыта, естественно, полученного при работе с кодом других людей :-)

Определить список лучших практик всегда сложно.
Мне нравится думать, что мы определяем протокол связи, потому что это так. 
Поэтому в этом документе используются ключевые слова, определенные в [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt), 
чтобы различать правила, которые никогда не следует нарушать, 
и те, которые можно нарушать, если вы знаете, что делаете.

Этот список также далеко не полный.

### 0.1. НЕЛЬЗЯ слепо следовать советам

Это должно быть первым правилом любого документа, описывающего лучшие практики.

Всегда старайтесь понять причины правил, не занимайтесь карго-культом. 
Слепое следование советам приводит к самым ужасным неприятностям, которые только можно себе представить.

## 1. Гигиенические правила

Это гигиенические правила общего назначения, выходящие за рамки правил языка или платформы. 
Язык программирования — это форма общения, ориентированная не только на компьютерные системы, 
но и на ваших коллег и на вас самих в будущем, поэтому соблюдать эти правила — 
это все равно, что мыть руки после посещения туалета.

### 1.1. СЛЕДУЕТ обеспечить разумную длину строки

Существует целая наука о типографике, которая утверждает, что люди теряют концентрацию, 
когда строка текста слишком широкая, из-за длинной строки трудно определить, где она начинается или заканчивается, 
и затрудняется переход к следующей строке под ней, 
поскольку вашим глазам приходится много двигаться справа налево. 
Это также затрудняет поиск важных деталей.

В типографике оптимальной длиной строки считается длина где-то между 50 и 70 символами.

В программировании используются отступы, поэтому невозможно установить длину строк в 60 символов. 
Обычно ограничение в 80 символов приемлемо, но не в Scala, потому что в Scala мы используем много замыканий. 
И если вам нужны длинные и описательные имена для функций и классов, то 80 символов — это слишком мало.

С другой стороны, 120 символов, на которые IntelliJ IDEA настроен по умолчанию, могут быть слишком широкими. 
Да, я знаю, что у нас есть мониторы с соотношением сторон 16:9, но это не улучшает читаемость, 
и с более короткими строками мы можем эффективно использовать эти широкие мониторы при параллельном сравнении. 
А в случае длинных строк требуется приложить усилия, чтобы заметить важные детали, которые происходят в конце этих строк.

Итак, баланс:

- стремитесь к 80 символам в качестве мягкого ограничения,
- и если этого станет недостаточно, тогда 100 символов, кроме...
- сигнатуры функций, которые могут стать очень уродливыми, если их ограничить

С другой стороны, все, что выходит за рамки 120 символов, является мерзостью.

### 1.2. НЕЛЬЗЯ полагаться на плагин SBT или IDE, который выполнит форматирование за вас.

Плагины IDE и SBT могут оказаться очень полезными, 
однако, если вы подумываете об использовании их для автоматического форматирования кода, будьте осторожны.

Вы не найдете плагин, способный определить намерения разработчика, 
поскольку это требует человеческого понимания кода и его практически невозможно реализовать. 
Цель правильных отступов и форматирования — не следовать каким-то жестким правилам, 
установленным для вас в духе карго-культа, 
а сделать код более логичным, более читабельным и доступным. 
На самом деле отступы — это форма искусства, и в этом нет ничего плохого, 
поскольку все, что вам нужно, — это чуйка на ужасный код и желание этот ужасный код исправить. 
И в должностной инструкции разработчика есть требование о том, чтобы его код был чист.

Таким образом, автоматизированные средства - это хорошо, 
НО БУДЬТЕ ОСТОРОЖНЫ, чтобы не испортить тщательно отформатированный код других людей, иначе _I'll slap you in prose_.

Давайте подумаем о том, что я сказал: если строка слишком длинная, как плагин может ее разделить? 
Давайте поговорим об этой строке (реальный код):

```scala
val dp = new DispatchPlan(new Set(filteredAssets), start = startDate, end = endDate, product, scheduleMap, availabilityMap, Set(activationIntervals.get), contractRepository, priceRepository)
```

В большинстве случаев плагин просто выполняет усечение, и я видел много таких случаев на практике:

```scala
val dp = new DispatchPlan(Set(filteredAssets), start =
  startDate, end = endDate, product, scheduleMap, availabilityMap,
  Set(activationIntervals), contractRepository, priceRepository)
```

Теперь код не читается, не так ли? Я имею в виду, серьезно, это выглядит как блевотина. 
И именно такие результаты я вижу от людей, которые полагаются на работу плагинов. 
У нас также может быть такая версия:

```scala
val dp = new DispatchPlan(
  Set(filteredAssets),
  startDate,
  endDate,
  product,
  scheduleMap,
  availabilityMap,
  Set(activationIntervals),
  contractRepository,
  priceRepository
)
```

Выглядит намного лучше.
Но правда в том, что в других случаях это не так хорошо. 
Допустим, у нас есть строка, которую мы хотим разделить:

```scala
val result = service.something(param1, param2, param3, param4).map(transform)
```

Теперь размещать эти параметры в отдельной строке ужасно, как бы вы с этим ни справлялись:

```scala
// ужасно, потому что вызов map не виден
val result = service.something(
  param1,
  param2,
  param3,
  param4).map(transform)

// Это ужасно, потому что нарушает логический поток
val result = service.something(
  param1,
  param2,
  param3,
  param4
).map(transform)
```

Так было бы намного лучше:

```scala
val result = service
  .something(param1, param2, param3, param4)
  .map(transform)
```

Теперь уже лучше, не так ли? 
Конечно, иногда этот вызов настолько длинный, что его не хватает. 
Поэтому вам нужно прибегнуть к какому-то временному значению, например...

```scala
val result = {
  val instance =
    object.something(
      myAwesomeParam1,
      otherParam2,
      someSeriousParam3,
      anEvenMoreSoParam4,
      lonelyParam5,
      catchSomeFn6,
      startDate7
    )

  for (x <- instance) yield
    transform(x)
}
```

Конечно, иногда, код так сильно "воняет", что вам нужно заняться рефакторингом - 
например, слишком много параметров слишком много для одной функции ;-)

И мы говорим строго о длине строк — когда мы переходим к другим вопросам, все становится еще сложнее. 
На самом деле вы не найдете плагина, который бы проводил такой анализ и мог принять за вас правильное решение.

### 1.3. СЛЕДУЕТ разбивать длинные функции

В идеале функции должны иметь длину всего пару строк. 
Если строки становятся слишком большими, нужно разбить их на более мелкие функции и дать им имя.

Обратите внимание, что в Scala нам не обязательно делать такие промежуточные функции доступными в других областях видимости, 
цель здесь — в первую очередь улучшить читаемость, поэтому в Scala мы можем использовать внутренние функции, 
чтобы разбить логику на части.

### 1.4. НЕЛЬЗЯ допускать орфографических ошибок в именах и комментариях

Орфографические ошибки ужасно раздражают, прерывая поток чтения. 
Используйте программу проверки орфографии. 
Интеллектуальные IDE имеют встроенные средства проверки правописания. 
Обратите внимание на подчеркнутые предупреждения об орфографии и исправьте их.

### 1.5. Имена ДОЛЖНЫ быть осмысленными

_"В информатике есть только две сложные вещи: аннулирование кэша и присвоение имен вещам."_ -- Phil Karlton

Здесь у нас есть три рекомендации:

1. давайте описательные имена, но не переусердствуйте, четыре слова — это уже слишком много
2. вы можете быть краткими в именовании, если тип/цель можно легко вывести из непосредственного контекста 
   или если уже существует установленное соглашение
3. если вы идете описательным путем, не употребляйте бессмысленных слов

Например, это приемлемо:

```scala
for (p <- people) yield
  transformed(p)
```

Мы видим, что `p` — это человек (_person_) из непосредственного контекста, 
поэтому можно использовать короткое имя из одной буквы.

Следующее также приемлемо, поскольку `i` является общепринятым соглашением об использовании в качестве индекса:

```scala
for (i <- 0 until limit) yield ???
```

В целом следующее неприемлемо, потому что обычно в случае кортежей название коллекции плохо отражает то, 
что содержится (если вы не дали этим элементам имя, то, как следствие, сама коллекция будет иметь плохое имя):

```scala
someCollection.map(_._2)
```

С другой стороны, короткие имена подходят для неявных параметров, 
поскольку они передаются неявно, мы не заботимся о неявных параметрах, если только они не отсутствуют:

```scala
def query(id: Long)(implicit ec: ExecutionContext, c: WSClient): Future[Response]
```

Следующее неприемлемо, потому что имя совершенно бессмысленно, 
даже если есть явная попытка быть описательным:

```scala
def processItems(people: Seq[Person]) = ???
```

Это неприемлемо, поскольку название этой функции указывает на побочный эффект 
(процесс (`process`) — это глагол, обозначающий команду), 
но оно не описывает, что мы делаем с этими людьми (`people`). 
Суффикс `Items` не имеет смысла, потому что мы могли бы сказать `processThingy`, `processRows`, `processStuff`, 
и это все равно будет говорить то же самое — абсолютно ничего. 
Это также увеличивает визуальный беспорядок, поскольку чем больше слов, тем больше текста нужно прочитать, 
а бессмысленные слова — это просто шум.

Правильно подобранные описательные названия – хорошо. Бредовые имена – плохо.

## 2. Языковые правила

### 2.1. НЕЛЬЗЯ использовать "return"

Оператор `return` из Java сигнализирует о побочном эффекте — 
раскручивает стек и передает заданное значение вызывающему объекту. 
В языке, в котором упор делается на программирование, полное побочных эффектов, это имеет смысл. 
Однако Scala — это язык, ориентированный на выражения, 
в котором упор делается на контроль/ограничение побочных эффектов, а `return` не является идиоматическим.

Что еще хуже, `return`, вероятно, ведет себя не так, как вы думаете. 
Например, попробуйте сделать следующее в контроллере Play:

```scala
def action = Action { request =>
  if (someInvalidationOf(request))
    return BadRequest("bad")

  Ok("all ok")
}
```

В Scala оператор `return` внутри вложенной анонимной функции реализуется путем выдачи 
и перехвата исключения `NonLocalReturnException`. 
Об этом говорится в
[Scala Language Specification, section 6.20](https://scala-lang.org/files/archive/spec/2.13/spec.pdf).

Кроме того, `return` - это антиструктурное программирование, 
так как функции могут быть описаны с несколькими точками выхода, 
и если вам нужен `return`, как в тех гигантских методах с множеством ветвей if/else, 
наличие `return` - явный сигнал о том, что код плохой, 
магнит для будущих ошибок и поэтому нуждается в срочном рефакторинге.

### 2.2. СЛЕДУЕТ использовать неизменяемые структуры данных

Давайте проиллюстрируем:

```scala
trait Producer[T] {
 def fetchList: List[T]
}

// на стороне потребителя
someProducer.fetchList
```

Вопрос: если `List`, возвращенный выше, является изменяемым, что это говорит об интерфейсе `Producer`?

Вот некоторые проблемы:

1. если этот список создается в другом потоке, чем потребитель, 
   могут возникнуть проблемы как с видимостью, так и с атомарностью - 
   вы не можете знать, произойдет ли это, если не посмотрите на реализацию `Producer`.
2. даже если этот список фактически неизменяем 
   (т.е. все еще изменяем, но больше не изменяется после того, как он был передан `Consumer`), 
   вы не знаете, будет ли он передан другим `Consumer`-ам, которые могут изменить его самостоятельно, 
   поэтому вы не можете рассуждать о том, что с этим можно сделать.
3. даже если описано, что доступ к этому `List` должен быть синхронизирован, 
   проблема в том - на каком lock вы собираетесь синхронизироваться? 
   Вы уверены, что получите правильный порядок блокировки? Lock-и не являются составными.

Итак, вот что получаем: общедоступный API, раскрывающий изменяемую структуру данных, 
является натуральной мерзостью, приводящей к проблемам, 
которые могут быть хуже, чем те, которые возникают при ручном управлении памятью.

### 2.3. НЕ СЛЕДУЕТ обновлять `var`, используя циклы или условия

Это ошибка, которую совершает большинство Java-разработчиков, когда они переходят на Scala. Пример:

```scala
var sum = 0
for (elem <- elements) {
  sum += elem.value
}
```

Избегайте этого, вместо этого отдавайте предпочтение доступным операторам, например, `foldLeft`:

```scala
val sum = elements.foldLeft(0)((acc, e) => acc + e.value)
```

Или, что еще лучше, знайте стандартную библиотеку и всегда предпочитайте использовать встроенные функции — 
чем выразительнее вы будете действовать, тем меньше ошибок у вас будет:

```scala
val sum = elements.map(_.value).sum
```

Точно так же не следует обновлять частичный результат условием.
Пример:

```scala
def compute(x) = {
  var result = resultFrom(x)

  if(needToAddTwo) {
    result += 2
  }
  else {
    result += 1
  }

  result
}
```

Предпочитайте выражения и неизменяемость. 
Код станет более читабельным и менее подверженным ошибкам, поскольку ветки становятся более явными, и это хорошо:

```scala
def computeResult(x) = {
  val r = resultFrom(x)
  if (needToAddTwo)
    r + 2
  else
    r + 1
}
```

И знаете, как только ветки становятся слишком сложными, 
как было сказано в обсуждении `return`, 
это признак того, что код пахнет и нуждается в рефакторинге, и это хорошо.

### 2.4. НЕ СЛЕДУЕТ определять бесполезные trait-ы

Была такая Java Best Practice, 
в которой говорилось: _"программировать для интерфейса, а не для реализации"_ 
("_program to an interface, not to an implementation_"), 
лучшая практика, которая была культивирована до такой степени, 
что люди начали определять в своем коде совершенно бесполезные интерфейсы. 
Вообще говоря, это правило полезно, но оно относится к общей инженерной необходимости скрывать детали реализации, 
особенно детали изменения состояния (инкапсуляции), а не наносить удары по объявлениям интерфейса, 
которые в любом случае часто приводят к утечке деталей реализации.

Определение trait-ов также является бременем для читателей этого кода, 
поскольку оно сигнализирует о необходимости полиморфизма. 
Пример:

```scala
trait PersonLike {
  def name: String
  def age: Int
}

case class Person(name: String, age: Int)
  extends PersonLike
```

Читатели этого кода могут прийти к выводу, что существуют случаи, 
когда переопределение `PersonLike` желательно. 
Это очень далеко от истины: `Person` прекрасно описывается своим кейс-классом как структура данных без поведения. 
Другими словами, он описывает форму ваших данных, 
и если вам нужно переопределить эту форму по какой-то неизвестной причине, 
то этот trait определен плохо, поскольку он навязывает форму ваших данных, 
и это единственное, что вы можете переопределить. 
Вы всегда можете придумать trait-ы позже, если вам понадобится полиморфизм, 
после того, как ваши потребности изменятся.

И если вы думаете, что вам, возможно, придется переопределить источник этого 
(например, чтобы получить имя человека из БД при первом доступе), 
Боже мой, не делайте этого!

Обратите внимание, что я не говорю об алгебраических структурах данных 
(т.е. о закрытых trait-ах, которые сигнализируют о закрытом наборе вариантов — например, `Option`).

Даже в тех случаях, когда вы думаете, что проблема ясна, это может быть не так. 
Давайте возьмем этот пример:

```scala
trait DBService {
  def getAssets: Future[Seq[(AssetConfig, AssetPersistedState)]]

  def persistFlexValue(flex: FlexValue): Future[Unit]
}
```

Этот фрагмент взят из реального кода — у нас есть `DBService`, 
который обрабатывает либо запросы, либо сохранение в базе данных. 
Эти два метода на самом деле не связаны друг с другом, поэтому, 
если вам нужно только получить ресурсы, зачем зависеть от вещей, 
которые вам не нужны в компонентах, требующих взаимодействия с БД?

В последнее время мой код выглядит примерно так:

```scala
final class AssetsObservable
    (f: => Future[Seq[(AssetConfig, AssetPersistedState)]])
  extends Observable[AssetConfigEvent] {

  // ...
}

object AssetsObservable {
  // constructor
  def apply(db: DBService) = new AssetsObservable(db.getAssets)
}
```

Видите ли, мне не нужно имитировать весь `DBService`, чтобы протестировать вышеизложенное.

### 2.5. НЕЛЬЗЯ использовать "var" внутри case class

Кейс-классы представляют собой синтаксический сахар для определения классов, в которых:
все аргументы конструктора являются общедоступными и неизменяемыми 
и, следовательно, являются частью идентификатора значения, 
имеют структурное равенство, соответствующую реализацию `hashCode` 
и автоматически сгенерированные функции `apply`/`unapply`, предоставляемые компилятором.

Делая это:

```scala
case class Sample(str: String, var number: Int)
```

Вы только что нарушили операцию равенства и хэш-кода. 
Теперь попробуйте использовать его как ключ в ассоциативном массиве.

Как правило, структурное равенство работает только для неизменяемых вещей, 
поскольку операция равенства должна быть стабильной (и не меняться в зависимости от истории объекта). 
Кейс-классы предназначены для строго неизменяемых вещей. 
Если вам нужно что-то изменить, не используйте кейс-классы.

Приблизительно говоря словами Fogus в "The Joy of Clojure" или Baker в его статье 1993 года: 
если любые два изменяемых объекта считаются равными сейчас, 
то нет никакой гарантии, что тоже самое будет верно и через мгновение. 
А если два объекта не всегда равны, то технически они никогда не равны ;-)

### 2.6. НЕ СЛЕДУЕТ объявлять абстрактные члены "var"

Объявлять абстрактные переменные в абстрактных классах или трейтах — плохая практика. 
Не делай это:

```scala
trait Foo {
  var value: String
}
```

Вместо этого предпочтите объявлять абстрактные вещи как `def`:

```scala
trait Foo {
  def value: String
}

// затем может быть переопределено как угодно
class Bar(val value: String) extends Foo
```

Причина связана с наложенным ограничением - `var` можно переопределить только с помощью `var`. 
Способ предоставить свободу выбора наследования — использовать `def` для абстрактных членов. 
И зачем вам налагать ограничение на использование `var` для тех, которые наследуются от вашего интерфейса. 
`def` является общим, поэтому используйте его.

### 2.7. НЕЛЬЗЯ создавать исключения для проверки пользовательского ввода или управления потоком данных

Две причины:

1. это противоречит принципам структурированного программирования, 
   поскольку процедура в конечном итоге имеет несколько точек выхода, и поэтому ее труднее обсуждать - 
   поскольку раскручивание стека является ужасным и часто непредсказуемым побочным эффектом
2. исключения не документированы в сигнатуре функции — 
   Java попыталась исправить это с помощью концепции проверенных исключений, 
   что на практике было ужасно, поскольку люди их просто игнорировали.

Исключения полезны только для одного — сигнализировать о неожиданных ошибках (ошибках) вверх по стеку, 
чтобы супервизор мог отловить эти ошибки и принять решение о дальнейших действиях, 
таких как регистрация ошибок, отправка уведомлений, перезапуск виновного компонента и т.д.

В качестве апелляции к авторитету разумно сослаться на главу 4 ["Functional Programming with Scala"](http://www.manning.com/bjarnason/).

### 2.8. НЕЛЬЗЯ перехватывать Throwable при перехвате исключений

Никогда, никогда, никогда не делайте этого:

```scala
try {
 something()
} catch {
 case ex: Throwable =>
   blaBla()
}
```

Никогда не перехватывайте `Throwable`, потому что мы можем говорить о чрезвычайно фатальных исключениях, 
которые никогда не следует перехватывать и которые могут привести к сбою процесса. 
Например, если JVM выдает ошибку нехватки памяти, 
даже если вы повторно выдадите это исключение в этом предложении `catch`, 
может быть слишком поздно - учитывая, что процессу не хватает памяти, 
сборщик мусора, вероятно, взял на себя управление и все заморозил, 
при этом процесс заканчивается неизлечимым состоянием зомби. 
Это означает, что внешний супервизор (например, Upstart) не получит возможности его перезапустить.

Вместо этого сделайте следующее:

```scala
import scala.util.control.NonFatal

try {
 something()
} catch {
 case NonFatal(ex) =>
   blaBla()
}
```

### 2.9. НЕЛЬЗЯ использовать "null"

Вы должны избегать использования `null`. Вместо этого предпочитайте `Option[T]` Scala. 
Значения `null` подвержены ошибкам, поскольку компилятор не может вас защитить. 
Значения, допускающие `null`, которые встречаются в определениях функций, не документируются в этих определениях. 
Поэтому избегайте этого:

```scala
def hello(name: String) =
  if (name != null)
    println(s"Hello, $name")
  else
    println("Hello, anonymous")
```

В качестве первого шага вы можете сделать следующее:

```scala
def hello(name: Option[String]) = {
  val n = name.getOrElse("anonymous")
  println(s"Hello, $n")
}
```

Смысл использования `Option[T]` в том, что компилятор заставляет вас так или иначе с этим справляться:

1. вам либо придется разобраться с этим сразу (например, предоставив значение по умолчанию, выдав исключение и т.д.)
2. или вы можете распространить полученный `Option` вверх по стеку вызовов

Также помните, что `Option` — это набор из 0 или 1 элементов, 
поэтому вы можете использовать `foreach`, что совершенно идиоматично:

```scala
val name: Option[String] = ???

for (n <- name) {
  // выполняется только тогда, когда имя определено
  println(n)
}
```

Объединить несколько `Option` также легко:

```scala
val name: Option[String] = ???
val age: Option[Int] = ???

for (n <- name; a <- age)
  println(s"Name: $n, age: $a")
```

А поскольку `Option` тоже рассматривается как `Iterable`, 
вы можете использовать `flatMap` для коллекций, чтобы избавиться от значений `None`:

```scala
val list = Seq(1,2,3,4,5,6)

list.flatMap(x => Some(x).filter(_ % 2 == 0))
// => 2,4,6
```

### 2.10. НЕЛЬЗЯ использовать `Option.get`

У вас может возникнуть соблазн сделать это:

```scala
val someValue: Option[Double] = ???

// ....
val result = someValue.get + 1
```

Никогда не делайте этого, поскольку вы обмениваете исключение `NullPointerException` 
на исключение `NoSuchElementException`, а это в первую очередь противоречит цели использования `Option`.

Альтернативы:

1. использование `Option.getOrElse`
2. использование `Option.fold`
3. использование сопоставления с образцом и явная работа с веткой `None`
4. не вынимать значение из его необязательного контекста

В качестве примера для (4) отсутствие выдергивания значения из контекста означает следующее:

```scala
val result = someValue.map(_ + 1)
```

### 2.11. НЕЛЬЗЯ использовать `Date` или `Calendar` Java, вместо этого используйте `java.time` (JSR-310)

Классы Java `Date` и `Calendar` из стандартной библиотеки ужасны, потому что:

1. результирующие объекты являются изменяемыми, что не имеет смысла для выражения даты, 
   которая должна быть значением (как бы вы себя чувствовали, 
   если бы вам приходилось работать со `StringBuffer` везде, где есть строки?)
2. нумерация месяцев начинается с нуля
3. `Date`, в частности, не сохраняет информацию о часовом поясе, поэтому значения даты совершенно бесполезны.
4. нет разницы между GMT и UTC
5. годы выражаются 2 цифрами вместо 4

Вместо этого всегда используйте API [`java.time`](https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html), 
представленный в Java 8, или, если вы застряли в стране до Java 8, 
используйте [Joda-Time](http://www.joda.org/joda-time), который является его духовным предком.

### 2.12. НЕ СЛЕДУЕТ использовать `Any` или `AnyRef` или `isInstanceOf`/`asInstanceOf`.

Избегайте использования `Any` или `AnyRef` или явного приведения типов, 
если только у вас нет для этого действительно веской причины. 
Scala — это язык, который извлекает пользу из своей выразительной системы типов, 
использование `Any` или приведения типов представляет собой дыру в этой выразительной системе типов, 
и компилятор не знает, как вам в этом помочь. В общем, вот так плохо:

```scala
val json: Any = ???

if (json.isInstanceOf[String])
  doSomethingWithString(json.asInstanceOf[String])
else if (json.isInstanceOf[Map])
  doSomethingWithMap(json.asInstanceOf[Map])
else
  ???
```

Часто мы используем `Any` при десериализации. 
Вместо того, чтобы работать с `Any`, подумайте о желаемом универсальном типе 
и наборе необходимых подтипов и придумайте алгебраический тип данных (ADT - Algebraic Data-Type):

```scala
sealed trait JsValue

case class JsNumber(v: Double) extends JsValue
case class JsBool(v: Boolean) extends JsValue
case class JsString(v: String) extends JsValue
case class JsObject(map: Map[String, JsValue]) extends JsValue
case class JsArray(list: Seq[JsValue]) extends JsValue
case object JsNull extends JsValue
```

Теперь вместо того, чтобы работать с `Any`, мы можем выполнять сопоставление с образцом для `JsValue`, 
и компилятор может помочь нам здесь с недостающими ветвями, поскольку выбор конечен. 
Следующее вызовет предупреждение об отсутствующих ветках:

```scala
val json: JsValue = ???
json match {
  case JsString(v) => doSomethingWithString(v)
  case JsNumber(v) => doSomethingWithNumber(v)
  // ...
}
```

### 2.13. НУЖНО сериализовать даты как timestamp Unix или как ISO 8601

Timestamp Unix, при условии, что речь идет о количестве секунд или миллисекунд с 1970-01-01 00:00:00 UTC (с акцентом на UTC), 
являются достойным кроссплатформенным форматом сериализации. 
У него есть тот недостаток, что он имеет ограничения в том, что он может выражать. 
ISO-8601 — достойный формат сериализации, поддерживаемый большинством библиотек.

Избегайте чего-либо еще, а также при хранении дат без прикрепленного часового пояса (например, в MySQL) 
всегда выражайте эту информацию в формате UTC.

### 2.14. НЕЛЬЗЯ использовать магические значения

Хотя в других языках нередко используются «магические» (специальные) значения, такие как `-1`, 
для обозначения определенных результатов, в Scala существует ряд типов, позволяющих прояснить намерение. 
`Option`, `Either`, `Try` — тому примеры. 
Кроме того, если вы хотите выразить нечто большее, чем просто логический успех или неудачу, 
вы всегда можете придумать алгебраический тип данных.

Не делайте этого:

```scala
val index = list.find(someTest).getOrElse(-1)
```

### 2.15. НЕ СЛЕДУЕТ использовать `var` в качестве общего состояния

Избегайте использования "var", по крайней мере, когда речь идет об общем изменяемом состоянии. 
Потому что, если у вас есть общее состояние, выраженное в виде переменных, 
вам лучше синхронизировать его, и оно быстро станет уродливым. 
Гораздо лучше избегать этого. 
Если вам действительно нужно изменяемое общее состояние, 
используйте атомарную ссылку и храните в ней неизменяемые вещи. 
Также ознакомьтесь с [Scala-STM](https://nbronson.github.io/scala-stm/).

Итак, вместо чего-то вроде этого:

```scala
class Something {
  private var cache = Map.empty[String, String]
}
```

Если вы действительно не можете избежать переменной `cache`, лучше сделайте так:

```scala
import java.util.concurrent.atomic._

class Something {
  private val cache =
    new AtomicReference(Map.empty[String, String])
}
```

Да, это приводит к накладным расходам из-за необходимой синхронизации, 
что в случае атомарной ссылки означает циклы вращения (spin loops). 
Но это избавит вас от множества головных болей в дальнейшем. 
И лучше всего полностью избегать мутаций.

### 2.16. Публичные методы ДОЛЖНЫ явно указывать тип возвращаемого значения

Предпочитаю это:

```scala
def someFunction(param1: T1, param2: T2): Result = {
  ???
}
```

Чем это:

```scala
def someFunction(param1: T1, param2: T2) = {
  ???
}
```

Да, вывод типа по результату функции — это здорово и все такое, но для публичных методов:

1. нельзя полагаться на IDE или проверять реализацию, чтобы увидеть возвращаемый тип.
2. Scala в настоящее время выводит наиболее специализированный тип из возможных, 
   потому что в Scala тип возвращаемого значения для функций является ковариантным, 
   поэтому вы можете получить действительно уродливый тип.

Например, какой тип возвращает эта функция:

```scala
def sayHelloRunnable(name: String) = new Runnable {
  def sayIt() = println(s"Hello, $name")
  def run() = sayIt()
}
```

Как вы думаете, это `Runnable`?
Неверно, это `Runnable{ def sayIt(): Unit }`.

В качестве побочного эффекта это также увеличивает время компиляции, поскольку всякий раз, 
когда `sayHelloRunnable` меняет реализацию, он также меняет сигнатуру, поэтому все, 
что от нее зависит, необходимо перекомпилировать.

### 2.17. НЕ СЛЕДУЕТ определять кейс-классы, вложенные в другие классы

Это заманчиво, но почти никогда не следует определять вложенные кейс-классы внутри другого объекта/класса, 
поскольку это мешает Java сериализации. 
Причина в том, что когда вы сериализуете кейс-класс, он закрывается по указателю "this" 
и сериализует весь объект, что, если вы помещаете в свой `App` объект, означает, 
что для каждого экземпляра кейс-класса вы сериализуете весь мир.

И что особенно важно в случае с классами:

1. ожидается, что кейс-класс будет неизменяемым (значение, факт) и, следовательно,
2. ожидается, что кейс-класс будет легко сериализоваться

Предпочитайте плоские иерархии.

### 2.18 НЕЛЬЗЯ включать классы, трейты и объекты внутри объектов пакета

Классы, включая кейс-классы, трейты и объекты, не принадлежат объектам пакета. 
Это ненужно, сбивает с толку компилятор и поэтому не рекомендуется. 
Например, воздержитесь от следующих действий:

```scala
package foo

package object bar {
  case object FooBar
}
```

Тот же эффект достигается, если все артефакты находятся внутри простого пакета:

```scala
package foo.bar

case object FooBar
```

Объекты пакета должны содержать только определения значений, методов и псевдонимов типов и т.д. 
Scala допускает использование нескольких общедоступных классов в одном файле,
и в таких случаях по соглашению первая буква имени файла должна быть строчной. 

#### Неявные классы значений могут быть определены в объекте пакета

В одном редком случае имеет смысл включить классы, определенные непосредственно в `package object`. 
Причина этого в том, что неявные классы должны быть вложены в другой объект/класс, 
и вы не можете определить верхнеуровневый implicit в Scala. 
Вложение неявных классов значений внутри `package object` также позволяет создать удобный интерфейс импорта для библиотеки, 
поскольку при одном импорте объекта пакета будут добавлены все необходимые неявные элементы. 

Обойти это также невозможно, поскольку определение неявного класса значения означает, 
что мы не можем эффективно определить неявное значение и класс отдельно, 
весь смысл в том, чтобы позволить компилятору избежать упаковки во время выполнения, 
генерируя весь "правильный код" во время компиляции, прогнозируя время выполнения упаковки. 
Это оптимальный способ добиться повышения производительности шаблона библиотеки, 
и нам нужен весь синтаксис "в одном месте".

```scala
package object dsl {
  implicit class DateTimeAugmenter(val date: Datetime) extends AnyVal {
    def yesterday: DateTime = date.plusDays(-1)
  }
}
```

### 2.19 СЛЕДУЕТ использовать head/tail и init/last декомпозицию только если они могут быть выполнены за константное время и память

Пример head/tail разложения:

```scala
def recursiveSumList(numbers: List[Int], accumulator: Int): Int =
  numbers match {
    case Nil =>
      accumulator

    case head :: tail =>
      recursiveSumList(tail, accumulator + head)
  }
```

В `List` есть специальный head/tail экстрактор `::`, потому что списки всегда **создаются** путем добавления элемента в начало списка:

```scala
val numbers = 1 :: 2 :: 3 :: Nil
```

Это то же самое, что:

```scala
val numbers = Nil.::(3).::(2).::(1)
```

По этой причине и `head`, и `tail` списка требуют только постоянного времени и памяти! 
Эти операции имеют размер `O(1)`. 
Существует еще один head/tail экстрактор под названием `+:`, который работает с любым `Seq`:

```scala
def recursiveSumSeq(numbers: Seq[Int], accumulator: Int): Int =
  numbers match {
    case Nil =>
      accumulator

    case head +: tail =>
      recursiveSumSeq(tail, accumulator + head)
  }
```

Вы можете найти реализацию `+:` [здесь](https://github.com/scala/scala/blob/v2.12.4/src/library/scala/collection/SeqExtractors.scala). 
Проблема в том, что другие коллекции, кроме `List`, не обязательно разлагаются по head/tail за постоянное время и память,
например, `Array`:

```scala
val numbers = Array.range(0, 10000000)

recursiveSumSeq(numbers, 0)
```

Это крайне неэффективно: каждый `tail` на `Array` требует `O(n)` времени и памяти, 
поскольку каждый раз необходимо создавать новый массив! 
К сожалению, библиотека коллекций Scala допускает подобные неэффективные операции. 
Мы должны следить за ними.

---

Примером эффективной декомпозиции init/last является `scala.collection.immutable.Queue`. 
Он поддерживается двумя списками, а эффективность `head`, `tail`, `init` и `last` 
_амортизируется постоянным_ временем и памятью, как объяснено в 
[Scala collection performance characteristics](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html). 

Я не думаю, что разложение init/last является таким уж распространенным явлением. 
В целом это аналог head/tail разложения.
init/last деконструктор для любой `Seq` — это `:+`.

### 2.20 Нельзя использовать `Seq.head`

У вас может возникнуть соблазн:

```scala
val userList: List[User] = ???

// ....
val firstName = userList.head.firstName
```

Никогда не делайте этого, так как при этом возникнет исключение `NoSuchElementException`, если последовательность пуста. 

Альтернативы:

1. использование `Seq.headOption`, возможно, в сочетании с `getOrElse` или сопоставлением с образцом

   Пример:

    ```scala
    val firstName = userList.headOption match {
        case Some(user) => user.firstName
        case _ => "Unknown"
      }
    ```

2. использование сопоставления шаблонов с оператором cons `::`, если вы имеете дело со `List` 

   Пример:

    ```scala
    val firstName = userList match {
     case head :: _ => head.firstName
     case _ => "Unknown"
    }
    ```

3. используя `NonEmptyList`, если требуется, чтобы список никогда не был пустым. (См. [cats](https://typelevel.org/cats/datatypes/nel.html), [scalaz](https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/NonEmptyList.scala), ...)

### 2.21 Кейс-классы ДОЛЖНЫ быть final

Расширение кейс-класса приведет к неожиданному поведению. Следите за следующим:

```scala
scala> case class Foo(v:Int)
defined class Foo

scala> class Bar(v: Int, val x: Int) extends Foo(v)
defined class Bar

scala> new Bar(1, 1) == new Bar(1, 1)
res25: Boolean = true

scala> new Bar(1, 1) == new Bar(1, 2)
res26: Boolean = true
// ????
scala> new Bar(1,1) == Foo(1)
res27: Boolean = true

scala> class Baz(v: Int) extends Foo(v)
defined class Baz

scala> new Baz(1) == new Bar(1,1)
res29: Boolean = true //???

scala> println (new Bar(1,1))
Foo(1) // ???

scala> new Bar(1,2).copy()
res49: Foo = Foo(1) // ???
```

Поверьте: [почему кейс-классы должны быть final](https://stackoverflow.com/a/34562046/3856808)

Поэтому по умолчанию кейс-классы всегда должны определяться как final. 

Пример:

```scala
final case class User(name: String, id: Long)
```

### 2.22 НЕ СЛЕДУЕТ использовать `scala.App`

`scala.App` часто используется для обозначения точки входа приложения:

```scala
object HelloWorldApp extends App {
  println("hello, world!")
}
```

`DelayedInit`, один из механизмов, используемых для реализации `scala.App`, [устарел](https://github.com/scala/scala/pull/3563). 
Любые переменные, определенные в теле объекта, будут доступны как поля, если не будет применен модификатор `private`. 
Предпочитайте более простую альтернативу определения основного метода:

```scala
object HelloWorldApp {
  def main(args: Array[String]): Unit = println("hello, world!")
}
```

## 3. Архитектура приложения

### 3.1. НЕ СЛЕДУЕТ использовать Cake Pattern

Cake Pattern — [очень хорошая идея в теории](https://www.youtube.com/watch?v=yLbdw06tKPQ) — 
использование trait-ов в качестве модулей, которые можно компоновать, 
что дает вам возможность переопределять импорт с внедрением зависимостей во время компиляции в качестве побочного эффекта. 

На практике все реализации Cake, которые я видел, были ужасными, новые проекты следует избегать, 
а существующие проекты следует переносить с Cake. 

Люди неправильно реализуют Cake, поскольку это трудно понимаемый шаблон проектирования. 
Я не видел реализаций Cake, в которых trait-ы спроектированы как абстрактные модули 
или в которых уделяется должное внимание проблемам жизненного цикла. 
На практике происходит неряшливость, в результате чего получается большой комок шерсти. 
Замечательно, что Scala позволяет делать такие вещи, как Cake Pattern, 
подчеркивая реальную мощь ООП, но то, что вы можете, не означает, что вы должны это делать, 
потому что, если целью является внедрение зависимостей и развязка между различными компонентами, 
то вы потерпите неудачу и возложите бремя обслуживания на своих коллег. 

Например, в Cake это обычное явление:

```scala
trait SomeServiceComponent {
  type SomeService <: SomeServiceLike
  val someService: SomeService // abstract

  trait SomeServiceLike {
    def query: Rows
  }
}

trait SomeServiceComponentImpl extends SomeServiceComponent {
  self: DBServiceComponent =>

  val someService = new SomeService

  class SomeService extends SomeServiceLike {
    def query = dbService.query
  }
}
```

В приведенном выше примере `someService` фактически является [singleton](https://en.wikipedia.org/wiki/Singleton_pattern) 
и подлинным, поскольку в нем, вероятно, отсутствует _управление жизненным циклом_. 
И если, прочитав этот код, ваши тревоги не были вызваны отсутствием единичного управления жизненным циклом, 
что ж, познакомьтесь с ужасным секретом большинства реализаций Cake. 
И те немногие сознательные, кто делает это правильно, попадают в ад инициализации JVM. 

Но это не единственная проблема. 
Более серьезная проблема заключается в том, что разработчики ленивы, 
поэтому в итоге вы получаете огромные компоненты с десятками зависимостей 
и обязанностей, потому что Cake поощряет это. 
И после того, как первоначальные разработчики, которые нанесли этот ущерб, уходят из проекта, 
вы получаете другие, меньшие компоненты, которые дублируют функциональность исходных компонентов, 
просто потому, что оригинальные компоненты адски тестировать, 
потому что вам нужно имитировать или заглушить слишком много вещей (еще один запах кода). 
И у вас есть этот вечно повторяющийся цикл: разработчики в конечном итоге ненавидят базу кода, 
выполняют минимальный объем работы, необходимый для выполнения своих задач, 
и в конечном итоге получают другие большие, уродливые и фундаментально ошибочные компоненты. 
А из-за жесткой связи, которую естественным образом создает Cake, их будет нелегко реорганизовать. 

Так зачем делать вышеизложенное, если что-то вроде этого гораздо более читабельно и имеет здравый смысл:

```scala
class SomeService(dbService: DBService) {
  def query = dbService.query
}
```

Или, если вам действительно нужны абстрактные вещи 
(но, пожалуйста, прочитайте [правило 2.4](#_2-4-не-следует-определять-бесполезные-trait-ы) 
о том, как не определять бесполезные trait-ы):

```scala
trait SomeService {
  def query: Rows
}

object SomeService {
  /** Builder for [[SomeService]] */
  def apply(dbService: DBService): SomeService =
    new SomeServiceImpl(dbService)

  private final class SomeServiceImpl(dbService: DBService)
    extends SomeService {
    def query: Rows = dbService.query
  }
}
```

Ваши зависимости сходят с ума? Эти конструкторы начинают болеть? Это особенность. 
Это называется "_развитие, основанное на боли_" (сокращенно PDD :-)). 
Это признак того, что архитектура не в порядке, 
и различные библиотеки внедрения зависимостей или Cake pattern решают не проблему, 
а симптомы, пряча мусор под ковриком.

Поэтому отдайте предпочтение старым и надежным _аргументам конструктора_.
А если вам действительно нужно использовать библиотеки внедрения зависимостей, 
делайте это по краям (как в контроллерах Play).
Потому что, если компонент зависит от слишком многих вещей, этот _код пахнет_. 
Если компонент зависит от трудно инициализируемых аргументов, этот _код пахнет_. 
Если вам нужно имитировать или заглушать интерфейсы в ваших тестах 
только для проверки чистой бизнес-логики, вероятно, _этот код пахнет_ ;-) 

Не прячьте болезненные вещи под ковер, а исправьте их.

### 3.2. НЕЛЬЗЯ помещать вещи в Play's Global 

Я вижу это снова и снова. 

Ребята, глобальный объект Play ([Play's Global](https://www.playframework.com/documentation/2.3.x/ScalaGlobal)) — 
это не ведро, в которое вы можете запихнуть потерянные фрагменты кода. 
Его цель — подключиться к конфигурации и жизненному циклу Play, не более того. 

Придумайте собственное пространство имен для своих утилит.

### 3.3. НЕ СЛЕДУЕТ применять оптимизацию без профилирования

Профилирование является обязательным условием для оптимизации. 
Никогда не работайте над оптимизацией, 
если только посредством профилирования вы не обнаружите реальные узкие места. 

Это связано с тем, что наша интуиция о том, как ведет себя система, часто нас подводит, 
и применение оптимизаций без точных цифр может привести к множественным эффектам: 

- вы можете усложнить код или архитектуру, 
  что затруднит глобальное применение последующих оптимизаций
- ваша работа может оказаться напрасной или привести к еще большему снижению производительности. 

Доступно несколько стратегий, и вам желательно использовать их все: 

- хороший профилировщик может рассказать вам о неочевидных узких местах, 
  мой любимый — YourKit Profiler, но VisualVM от Oracle бесплатен и зачастую достаточно хорош
- собирать метрики из работающих производственных систем с помощью такой библиотеки, 
  как [Dropwizard Metrics](https://dropwizard.github.io/metrics/3.1.0), 
  и использовать их в чем-то вроде [Graphite](http://graphite.wikidot.com) — 
  стратегии, которая может вести вас в правильном направлении
- сравнивайте решения, написав код для сравнительного анализа, 
  но учтите, что бенчмаркинг — это непростая задача, 
  и вам следует хотя бы использовать такую библиотеку, 
  как [JMH](http://openjdk.java.net/projects/code-tools/jmh/),
  [Scala Meter](https://scalameter.github.io/)
 
В целом – измеряйте, а не гадайте.

### 3.4. СЛЕДУЕТ помнить о сборщике мусора 

Не перераспределяйте ресурсы, если в этом нет необходимости. 
Мы хотим избегать микрооптимизаций, но всегда помните о том, 
какое влияние распределение может оказать на вашу систему. 

[По словам Мартина Томсона](http://www.infoq.com/presentations/top-10-performance-myths), 
если вы нагружаете сборщик мусора, вы увеличиваете задержку при зависаниях системы 
и количество таких случаев, при этом сборщик мусора действует как GIL 
и, таким образом, ограничивает производительность и вертикальную масштабируемость. 

Пример:

```scala
query.filter(_.someField.inSet(Set(name)))
```

Это пример, возникший в нашем проекте из-за проблемы с API Slick. 
Поэтому вместо теста `===` разработчик решил выполнить операцию `inSet` с последовательностью из 1 элемента. 
Такое выделение коллекции из 1 элемента происходит при каждом вызове метода. 
Это нехорошо, того, чего можно избежать, следует избегать. 

Другой пример:

```scala
someCollection
  .filter(Set(a,b,c).contains)
  .map(_.name)
```

Прежде всего, это создает `Set` каждый раз для каждого элемента нашей коллекции. 
Во-вторых, `filter` и `map` можно сжать за одну операцию, 
иначе мы получим больше мусора 
и больше времени потратим на построение итоговой коллекции:

```scala
val isIDValid = Set(a,b,c)

someCollection.collect {
  case x if isIDValid(x) => x.name
}
```

Общий пример, который часто появляется 
и иллюстрирует бесполезные обходы и операторы, которые можно сжать:

```scala
collection
  .filter(bySomething)
  .map(toSomethingElse)
  .filter(again)
  .headOption
```

Кроме того, обратите внимание на свои требования и используйте структуру данных, 
подходящую для вашего случая использования. 
Вы хотите построить стек? Это `List`. 
Вы хотите проиндексировать список? Это `Vector`. 
Вы хотите добавить в конец списка? Это снова `Vector`. 
Вы хотите добавлять в начало и забирать с конца? Это `Queue`. 
У вас есть множество элементов и вы хотите проверить вхождение? Это `Set`. 
У вас есть множество элементов, которые вы хотите держать в заданном порядке? Это `SortedSet`. 
Это не ракетостроение, просто информатика 101. 

Мы не говорим здесь о экстремальных микрооптимизациях, 
мы даже не говорим здесь о чем-то, специфичном для Scala, FP или JVM, 
но помните о том, что вы делаете, 
и старайтесь не создавать ненужных выделений, поскольку это намного сложнее позднее исправить. 

Кстати, есть очевидное решение для сохранения выразительности при фильтрации и сопоставлении — 
ленивые коллекции, что в Scala означает [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html#), 
если вам нужна меморизация, 
или [Iterable](http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html), если вам не нужна меморизация. 

Также обязательно прочтите [Правило 3.3](#_3-3-не-следует-применять-оптимизацию-без-профилирования) 
о профилировании.

### 3.5. НЕЛЬЗЯ использовать `ConfigFactory.load()` без параметров или напрямую обращаться к объекту `Config`

Может быть очень заманчиво вызвать метод `ConfigFactory.load()`, 
который очень доступен и не имеет параметров, 
всякий раз, когда вам нужно извлечь что-то из конфигурации, 
но это обернется против вас бумерангом, например, при написании тестов. 

Если у вас есть [`ConfigFactory.load()`](https://lightbend.github.io/config/latest/api/com/typesafe/config/ConfigFactory.html), 
разбросанный по всем вашим классам, 
то они, в основном, загружают конфигурацию по умолчанию при запуске вашего кода, 
что чаще всего не то, чего вы действительно хотите во время тестирования, 
где вам нужно загружзить измененную конфигурацию 
(например, разные таймауты, разные реализации, разные IP-адреса и т.д.). 

НИКОГДА не делайте этого:

```scala
class MyComponent {
  private val ip = ConfigFactory.load().getString("myComponent.ip")
}
```

Один из способов справиться с этим — передать сам экземпляр `Config` тому, 
кто в нем нуждается, или передать необходимые значения из него. 
Описанная здесь ситуация на самом деле является разновидностью 
[предпочтительного внедрения зависимостей (DI) над Service Locator](http://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old/1638961#1638961).

Вы можете вызвать `ConfigFactory.load()`, но из корня вашего приложения, 
скажем, в `main()` (или его эквиваленте), 
чтобы вам не приходилось жестко кодировать имя файла вашей конфигурации. 

Еще одна хорошая практика — иметь классы конфигурации, специфичные для домена, 
которые анализируются из объектов `Config` общего назначения, похожих на ассоциативные массивы. 
Преимущество этого подхода заключается в том, 
что специализированные классы конфигурации точно отражают ваши конкретные потребности в конфигурации 
и после анализа позволяют вам работать со скомпилированными классами более типобезопасным способом 
(где "безопаснее" означает, что вы используете `config.ip` вместо `config.getString("ip")`). 

Это также имеет преимущество ясности, поскольку класс конфигурации вашего домена 
передает необходимые свойства более явным и удобочитаемым образом. 

Рассмотрим следующий пример:

```scala
/** Это класс конфигурации, специфичный для вашего домена, с заранее определенным набором 
  * свойств, которые вы смоделировали в соответствии с вашим доменом, 
  * а не мешок свойств в виде ассоциативного массива
  */
case class AppConfig(
  myComponent: MyComponentConfig,
  httpClient: HttpClientConfig
)

/** Configuration for [[MyComponent]] */
case class MyComponentConfig(ip: String)

/** Configuration for [[HttpClient]] */
case class HttpClientConfig(
  requestTimeout: FiniteDuration,
  maxConnectionsPerHost: Int
)

object AppConfig {
  /** Загрузка вашего конфига.
    * Используется из `main()` или его эквивалента.
    */
  def loadFromEnvironment(): AppConfig =
    load(ConfigUtil.loadFromEnvironment())

  /** Загрузка из заданного объекта Typesafe Config */
  def load(config: Config): AppConfig =
    AppConfig(
        myComponent = MyComponentConfig(
          ip = config.getString("myComponent.ip")
        ),
        httpClient = HttpClientConfig(
          requestTimeout = config.getDuration("httpClient.requestTimeout", TimeUnit.MILLISECONDS).millis,
          maxConnectionsPerHost = config.getInt("httpClient.maxConnectionsPerHost")
        )
    )
}

object ConfigUtil {
  /** Утилита для замены прямого использования ConfigFactory.load() */
  def loadFromEnvironment(): Config = {
    Option(System.getProperty("config.file"))
      .map(f => ConfigFactory.parseFile(f).resolve())
      .getOrElse(
        ConfigFactory.load(System.getProperty(
          "config.resource", "application.conf")))
  }
}

/** Один компонент */
class HttpClient(config: HttpClientConfig) {
  ???
}

/** Другой компонент, в зависимости от конфигурации вашего домена. 
  * Также обратите внимание на разумное внедрение зависимостей ;-)
  */
class MyComponent(config: MyComponentConfig, httpClient: HttpClient) {
  ???
}
```

Преимущества этого подхода: 

- объекты конфигурации — это просто неизменяемые кейс-классы примитивов, 
  экземпляры которых можно легко создать
- ваши компоненты в конечном итоге зависят от конкретных и типобезопасных определений конфигурации, 
  относящихся только к ним, вместо получения монолитного и небезопасного `Config`, 
  который содержит все, и создание экземпляра которого обходится дорого
- и теперь ваша IDE может помочь с документацией и возможностью обнаружения
- и ваш компилятор может помочь с орфографическими ошибками 

> ПРИМЕЧАНИЕ о стиле: эти кейс-классы конфигурации имеют тенденцию становиться большими 
> и содержать примитивы (например, целые числа, строки и т.д.), 
> поэтому использование именованных параметров делает код более устойчивым к изменениям 
> и менее подверженным ошибкам по сравнению с использованием позиционирования. 
> Выбранный здесь стиль отступов делает экземпляр похожим на `Map` или объект JSON, если хотите.

## 4. Конкурентность и параллелизм

### 4.1. СЛЕДУЕТ избегать параллелизма, как чумы

По возможности избегайте необходимости иметь дело с параллелизмом.
Люди, хорошо разбирающиеся в параллелизме, избегают этого, как чумы.

**ВНИМАНИЕ:** проблемы параллелизма возникают не только при использовании общей памяти и потоков, 
но и между процессами, когда возникает конкуренция за какой-либо ресурс (например, базу данных).

Пример: если задание запланировано на выполнение каждую минуту с использованием cron.d в Linux 
и это задание извлекает и обновляет элементы из очереди, сохраненной в MySQL, 
выполнение этого задания может занять больше 1 минуты, 
и, таким образом, вы можете получить 2 или 3 процесса, 
выполняющихся одновременно и конкурирующих в одной и той же таблице MySQL.

### 4.2. СЛЕДУЕТ использовать соответствующие абстракции только там, где это возможно — Future, Actors, Rx

Узнайте о доступных абстракциях и выбирайте между ними в зависимости от поставленной задачи. 
Не существует общего универсального решения. 
Чем выше уровень абстракции, тем меньше возможностей у нее при решении проблем. 
Но чем меньше возможностей и мощности, тем проще и компонуемее модель. 
Например, многие разработчики из сообщества Scala злоупотребляют Akka Actors — 
это здорово, но только не тогда, когда их неправильно применяют. 
Например, не используйте Akka Actor, когда можно использовать `Future`.

> "_Власть имеет тенденцию развращать, а абсолютная власть развращает абсолютно_" — Лорд Эктон

Futures и Promises Scala хороши, потому что:

- они по своей сути поддаются распараллеливанию за счет устранения проблем параллелизма
- довольно эффективны, потому что при отправке задачи в неявный `ExecutionContext` 
  контекст выполнения по умолчанию эффективно мультиплексируется между несколькими потоками 
  (количество потоков в пуле потоков часто прямо пропорционально количеству имеющихся у вас ядер ЦП)
- альтернативные реализации могут быть даже более простыми и эффективными, 
  чем стандартное Future Scala, поскольку стандартное Future было разработано для общего назначения
- модель по своей сути проста и удобна в использовании

Futures и Promises плохи, потому что они сигнализируют 
только одно значение от производителя потребителю и все — 
если вам нужен поток или двунаправленная связь, 
Future может быть не лучшей абстракцией.

Akka Actor-ы хороши, потому что:

- они упрощают двунаправленную связь через асинхронные границы - 
  например, WebSocket является основным кандидатом на роль акторов.
- с помощью Actors Akka вы можете легко моделировать конечные автоматы (см. `context.become`)
- обработка сообщений имеет надежную гарантию отсутствия параллелизма — 
  сообщения обрабатываются одно за другим, 
  поэтому нет необходимости беспокоиться о проблемах параллелизма в контексте актора.
- вместо реализации недоделанной очереди в памяти для обработки вещей 
  вы можете просто использовать актора, поскольку постановка сообщений в очередь 
  и действие на эти сообщения — это то, что они делают.

Акторы Акки плохи, потому что:

- они довольно низкоуровневые для многих задач
- чрезвычайно легко моделировать акторов, которые сохраняют большое количество состояний, 
  в результате чего получается система, которую невозможно масштабировать по горизонтали
- из-за возможности двунаправленной связи очень легко получить потоки данных, 
  которые настолько сложны, что ими невозможно управлять
- моделью в целом является актор А, отправляющий сообщение актору Б, 
  но если вам нужно смоделировать поток событий, такая тесная связь между А и Б неприемлема
- акторы, используемые на практике с Akka, имеют тенденцию вызывать 
  неконтролируемые побочные эффекты и подвержены ошибкам, 
  что противоположно функциональному программированию, а не идиоматической Scala

Потоковые абстракции, такие как [Play's Iteratees](https://www.playframework.com/documentation/2.5.x/Iteratees) /
[Akka Streams](http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0-M2/scala.html) /
[RxJava](https://github.com/ReactiveX/RxJava) /
[Reactive Streams](http://www.reactive-streams.org/) /
[FS2](https://github.com/functional-streams-for-scala/fs2) /
[Monix](https://github.com/alexandru/monix), хороши, потому что:

- они моделируют однонаправленные коммуникации между производителями и потребителями
- события текут в одном направлении, поэтому вы легко трансформируете и компонуете эти потоки
- в зависимости от реализации они по умолчанию решают проблемы back-pressure
- как и в случае с Future, из-за ограничений модель проста в использовании 
  и гораздо более разумна и компонуема, чем акторы, а открытые операторы просто потрясающие

Потоки плохи, потому что:

- они касаются только однонаправленной связи, 
  что усложняется, если вам нужна двусторонняя связь, 
  поскольку акторы лучше ведут диалоги
- из-за их строгого контракта (например, отсутствия одновременных уведомлений) 
  реализация новых операторов и источников данных может быть проблематичной, 
  но из-за этого использование на стороне потребителя остается простым

Посмотрите презентацию Runar Bjarnason на эту тему, 
потому что она потрясающая: 
[Ограничения освобождают, свободы ограничивают](https://www.youtube.com/watch?v=GqmsQeSzMdw).

### 4.3. НЕ СЛЕДУЕТ заключать чистые операции, связанные с ЦП, в стандартные фьючерсы Scala

В целом это антипаттерн:

```scala
def add(x: Int, y: Int) = Future { x + y }
```

Если вы не видите там никакого ввода-вывода, это красный флаг. 
Необдуманное размещение вещей в `Future`-ах не решит ваших проблем с производительностью. 
Особенно в случае веб-сервера, на котором запросы уже распараллелены, 
и вышеописанное будет выполняться в ответ на запросы.
Запихивание частого CPU-bound в этом конструкторе `Future` 
замедлит выполнение вашей логики, а не ускорит ее. 

Кроме того, если вы хотите инициализировать `Future[T]` константой, 
всегда используйте `Future.successful()`.

### 4.4. НЕОБХОДИМО использовать Scala BlockContext для блокировки ввода-вывода

Сюда входят все блокирующие операции ввода-вывода, включая SQL-запросы. 
Реальный образец:

```scala
Future {
  DB.withConnection { implicit connection =>
    val query = SQL("select * from bar")
    query()
  }
}
```

Блокирующие вызовы подвержены ошибкам, 
поскольку необходимо точно знать, какой пул потоков будет затронут, 
и, учитывая конфигурацию серверного приложения по умолчанию, 
это может привести к недетерминированным взаимоблокировкам. 
Это ошибка, ожидающая своего проявления на проде. 

Вот упрощенный пример, демонстрирующий проблему в поучительных целях:

```scala
implicit val ec = ExecutionContext
  .fromExecutor(Executors.newFixedThreadPool(1))

def addOne(x: Int) = Future(x + 1)

def multiply(x: Int, y: Int) = Future {
  val a = addOne(x)
  val b = addOne(y)
  val result = for (r1 <- a; r2 <- b) yield r1 * r2

  // это зайдет в тупик 
  Await.result(result, Duration.Inf)
}
```

Этот пример упрощен, чтобы сделать эффект детерминированным, 
но это рано или поздно затронет все пулы потоков, 
настроенные с верхними границами. 

Блокирующие вызовы должны быть помечены вызовом `blocking`, 
который сигнализирует `BlockContext` об операции блокировки. 
Это очень аккуратный механизм в Scala, который сообщает `ExecutionContext` о том, 
что происходит операция блокировки, 
так что `ExecutionContext` может решить, что с этим делать, 
например, добавить больше потоков в пул потоков (что и делает пул потоков ForkJoin в Scala).

Весь код необходимо просмотреть, и всякий раз, 
когда происходит блокирующий вызов, это исправление:

```scala
import scala.concurrent.blocking
// ...
blocking {
  someBlockingCallHere()
}
```

> ПРИМЕЧАНИЕ. `blocking` вызов также служит документацией, 
> даже если базовый пул потоков не поддерживает `BlockContext`, 
> поскольку вещи, которые блокируют, совершенно неочевидны.

### 4.5. НЕ СЛЕДУЕТ блокировать

Иногда вам нужно заблокировать поток внизу — 
к сожалению, у JDBC нет неблокирующего API. 
Однако, если у вас есть выбор, никогда, никогда не блокируйте. 
Например, не делайте этого:

```scala
def fetchSomething: Future[String] = ???

// позднее ...
val result = Await.result(fetchSomething, 3.seconds)
result.toUpperCase
```

Предпочитаю полностью сохранять контекст этого `Future`:

```scala
def fetchSomething: Future[String] = ???

fetchSomething.map(_.toUpperCase)
```

Также проверьте [Scala-Async](https://github.com/scala/async), 
чтобы сделать это проще.

### 4.6. СЛЕДУЕТ использовать отдельный пул потоков для блокировки ввода-вывода

Что касается [правила 4.4](#_4-4-необходимо-использовать-scala-blockcontext-для-блокировки-ввода-вывода), 
если вы выполняете много блокирующих операций ввода-вывода (например, много вызовов JDBC), 
лучше создать второй пул потоков/контекст выполнения и выполнить все блокирующие вызовы на нем, 
оставив пул потоков приложения для работы с ресурсами, связанными с процессором. 

Итак, вы можете инициализировать этот второй контекст выполнения, например:

```scala
import java.util.concurrent.Executors

// ...
private val ioThreadPool = Executors.newCachedThreadPool(
  new ThreadFactory {
    private val counter = new AtomicLong(0L)

    def newThread(r: Runnable) = {
      val th = new Thread(r)
      th.setName("eon-io-thread-" + counter.getAndIncrement.toString)
      th.setDaemon(true)
      th
    }
  })
```

Обратите внимание, что здесь я предпочитаю использовать 
неограниченный "кешированный пул потоков", поэтому у него нет ограничений. 
При блокировании ввода-вывода идея состоит в том, 
что у вас должно быть достаточно потоков, которые вы можете заблокировать. 
Но если неограниченность — это слишком много, 
в зависимости от варианта использования вы можете позже ее настроить. 
Идея этого примера состоит в том, что вы можете начать работу. 

И тогда вы можете предоставить помощника, например:

```scala
def executeBlockingIO[T](cb: => T): Future[T] = {
  val p = Promise[T]()

  ioThreadPool.execute(new Runnable {
    def run() = try {
      p.success(blocking(cb))
    }
    catch {
      case NonFatal(ex) =>
        logger.error(s"Uncaught I/O exception", ex)
        p.failure(ex)
    }
  })

  p.future
}
```

Кроме того, не раскрывайте этот пул потоков ввода-вывода как контекст выполнения, 
который можно импортировать как неявную область видимости, 
потому что тогда люди по ошибке начнут использовать его для задач, 
связанных с процессором, 
поэтому лучше скрыть его и предоставить этот помощник.

### 4.7. Все общедоступные API ДОЛЖНЫ БЫТЬ поточно-ориентированными

Как общее правило разработки программного обеспечения поверх JVM: 
абсолютно все общедоступные API внутри вашего процесса 
в конечном итоге будут использоваться в контексте, 
в котором несколько потоков используют эти API в одно и то же время. 
Это лучшая практика для всех общедоступных API (все компоненты в вашем Cake, например) 
с самого начала быть спроектированным как потокобезопасное прохождение, 
потому что вы не хотите гоняться за ошибками параллелизма.

А если публичный API по какой-то причине не является потокобезопасным 
(как обычно компромиссы, сделанные при разработке программного обеспечения), 
затем укажите этот факт ЖИРНЫМИ ЗАГЛАВНЫМИ БУКВАМИ.

Причина проста: если API не является потокобезопасным, 
то и нет способа, которым пользователь этого API 
может узнать о способе его синхронизации. 

Пример:

```scala
val list = mutable.List.empty[String]
```

Допустим, этот изменяемый список открыт. 
По какой блокировке пользователь должен синхронизироваться? 
В самом списке? 
Этого недостаточно: это довольно бесполезная блокировка в более широком контексте. 
Помните, что блокировки не являются составными и очень подвержены ошибкам. 
Никогда не перекладывайте ответственность за синхронизацию 
из-за конфликтов на своих пользователей.

### 4.8. СЛЕДУЕТ избегать конфликтов при совместном чтении

Знакомьтесь: [Закон Амдала](https://en.wikipedia.org/wiki/Amdahl's_law). 
Синхронизация с блокировками резко ограничивает возможное распараллеливание 
и, таким образом, вертикальную масштабируемость. 
Чтения досадно распараллеливаются, поэтому никогда не делайте этого:

```scala
def fetch = synchronized { someValue }
```

Придумайте лучшие схемы синхронизации, не требующие синхронизация операций чтения, 
таких как атомарные ссылки или STM. 
Если вы не можете это сделать, то вообще избегайте этого, используя правильные абстракции.

### 4.9. НЕОБХОДИМО предоставить четко определенный и документированный протокол для каждого компонента или субъекта, который взаимодействует через границы асинхронности

Сигнатуры функции недостаточно для документирования протокола проблемных компонентов. 
Особенно если говорить о коммуникациях через границы асинхронности (между потоками, между сетевыми процессами и т.д.), 
протоколу требуется много подробностей о том, на что можно и нельзя полагаться.

В качестве рекомендации не уклоняйтесь от написания комментариев и документирования:

- проблемы параллелизма и задержки
- правильный порядок вызовов
- все, что может пойти не так

### 4.10. СЛЕДУЕТ всегда отдавать предпочтение сценариям с одним производителем

Совместная запись не подлежит распараллеливанию, 
тогда как совместное чтение досадно параллелизуемо. 
Метафора: 100 000 человек смогут посмотреть тот же футбольный матч на том же стадионе в то же время (читает), 
но 100 000 человек не могут пользоваться одной и той же ванной (пишет). 
В многопоточном сценарии предпочтительно один производитель / много потребителей.
Это позволяет избежать конфликтов и проблем с производительностью.
Приложение не масштабируется вертикально, 
когда несколько производителей работают над одним и тем же ресурсом, 
из-за закона Амдала.

Проверьте [LMAX Disruptor](https://lmax-exchange.github.io/disruptor).

### 4.11. НЕЛЬЗЯ жестко запрограммировать пул потоков/контекст выполнения

Это общая проблема дизайна, связанная с проектом в целом, но не делайте этого:

```scala
import scala.concurrent.ExecutionContext.Implicits.global

def doSomething: Future[String] = ???
```

Тесная связь между контекстом выполнения и вашей логикой не является хорошей идеей, 
а этот импорт является тесным, особенно потому, 
что в контексте приложения Play Framework вам необходимо использовать 
[другой пул потоков](https://www.playframework.com/documentation/2.5.x/ThreadPools).

Просто передайте `ExecutionContext` как неявный параметр.
Это идиоматично и приемлемо. 
Кроме того, неявные параметры следует передавать во второй группе параметров, 
чтобы не запутать неявное разрешение. 
При передаче в первой группе он позволяет вызывать такие методы, как `doSomething()`, 
которые не будут компилироваться, но большинство IDE покажут их действительными.

```scala
def doSomething()(implicit ec: ExecutionContext): Future[String] = ???
```

## 5. Akka акторы

### 5.1. СЛЕДУЕТ выделять состояние акторов только в ответ на сообщения, полученные извне

При использовании акторов Akka их изменяемое состояние 
всегда должно меняться в ответ на сообщения, полученные извне. 
Часто встречается вот такой антипаттерн:

```scala
class SomeActor extends Actor {
  private var counter = 0
  private val scheduler = context.system.scheduler
    .schedule(3.seconds, 3.seconds, self, Tick)

  def receive = {
    case Tick =>
      counter += 1
  }
}
```

В приведенном выше примере актор назначает `Tick` каждые 3 секунды, который меняет его состояние.
Это чрезвычайно дорогостоящая ошибка.
Поведение актора становится совершенно недетерминированным, 
и его невозможно проверить правильно.

Если вам действительно нужно периодически что-то делать внутри актора, 
то этот планировщик не должен инициализироваться внутри актора. 
Выньте это.

### 5.2. СЛЕДУЕТ изменять состояние акторов только с помощью `context.become` 

Скажем, у нас есть актор, который изменяет свое состояние (большинство акторов так делают), 
даже не имеет значения, какое это состояние:

```scala
class MyActor extends Actor {
  val isInSet = mutable.Set.empty[String]

  def receive = {
    case Add(key) =>
      isInSet += key
      
    case Contains(key) =>
      sender() ! isInSet(key)
  }
}

// Сообщения
case class Add(key: String)
case class Contains(key: String)
```

Поскольку мы используем Scala, мы хотим быть максимально чистыми,
мы хотим иметь дело с неизменяемыми структурами данных и чистыми функциями,
мы хотим перейти на FP, чтобы уменьшить область для [случайной сложности](https://en.wikipedia.org/wiki/No_Silver_Bullet), 
и позвольте мне сказать вам,
в вышеизложенном нет ничего чистого, неизменного или ссылочно прозрачного ;-)

Посмотрите [context.become](http://doc.akka.io/docs/akka/2.3.4/scala/actors.html#Become_Unbecome):

```scala
import collection.immutable.Set

class MyActor extends Actor {
  def receive = active(Set.empty)

  def active(isInSet: Set[String]): Receive = {
    case Add(key) =>
      context become active(isInSet + key)
      
    case Contains(key) =>
      sender() ! isInSet(key)
  }
}
```

Если это вас не сразу насторожило, просто подождите, 
пока вам придется смоделировать конечный автомат с 10 состояниями 
и десятками возможных переходов и эффектов, связанных с ним, 
и тогда вы это получите.

### 5.3. НЕЛЬЗЯ раскрывать внутреннее состояние актора в асинхронных замыканиях

Опять же, используя изменяемое состояние, определите проблему:

```scala
class MyActor extends Actor {
  val isInSet = mutable.Set.empty[String]

  def receive = {
    case Add(key) =>
      for (shouldAdd <- validate(key)) {
        if (shouldAdd) isInSet += key
      }
        
    // ...
  }
  
  def validate(key: String): Future[Boolean] = ???
}
```

Возникает хаос, двери ада открываются для целого ряда недетерминированных ошибок, 
которые могут произойти из-за проблем с многопоточностью.
Это общая проблема с функциями, которые выполняются асинхронно 
и захватывают переменные, которые не предназначены для выхода из контекста.
[Spores](http://docs.scala-lang.org/sips/pending/spores.html) — 
это предложение по замыканиям с поддержкой макросов, 
которые должны сделать это безопаснее, но до тех пор будьте осторожны.

Прежде всего, ознакомьтесь с правилом использования `context.become` для изменения состояния, 
что уже является шагом в правильном направлении.
И тогда вам нужно разобраться с этим, 
отправив нашему актору еще одно сообщение, когда наше будущее наступит:

```scala
import akka.pattern.pipeTo

class MyActor extends Actor {
  val isInSet = mutable.Set.empty[String]

  def receive = {
    case Add(key) =>
      val f = for (isValid <- validate(key))
        yield Validated(key, isValid)
        
      // отправка результата в виде сообщения обратно нашему актору
      f pipeTo self

    case Validated(key, isValid) =>
      if (isValid) isInSet += key
              
    // ...
  }
  
  def validate(key: String): Future[Boolean] = ???
}

// Сообщения
case class Add(key: String)
case class Validated(key: String, isValid: Boolean)
```

И, конечно же, мы могли бы моделировать конечный автомат, 
который не принимает больше запросов, пока не будет выполнен последний.
Давайте также избавимся от этой изменяемой коллекции и введем back-pressure
(т.е. нам нужно сообщить отправителю, когда он сможет отправить следующий элемент):

```scala
import akka.pattern.pipeTo

class MyActor extends Actor {
  def receive = idle(Set.empty)

  def idle(isInSet: Set[String]): Receive = {
    case Add(key) =>
      // отправка результата в виде сообщения обратно нашему актору
      validate(key).map(Validated(key, _)).pipeTo(self)
      
      // ожидание подтверждения
      context.become(waitForValidation(isInSet, sender()))
  }

  def waitForValidation(set: Set[String], source: ActorRef): Receive = {
    case Validated(key, isValid) =>
      val newSet = if (isValid) set + key else set
      // отправка подтверждения о завершении
      source ! Continue
      // вернуться в режим ожидания, принимая новые запросы
      context.become(idle(newSet))

    case Add(key) =>
      sender() ! Rejected
  }

  def validate(key: String): Future[Boolean] = ???
}

// Сообщения
case class Add(key: String)
case class Validated(key: String, isValid: Boolean)
case object Continue
case object Rejected
```

Да, проекты, основанные на акторах, могут оказаться непростыми.

### 5.4. СЛЕДУЕТ делать back-pressure

Допустим, у вас есть актор, производящий значения — 
например, чтение элементов из RabbitMQ или вашей собственной недоделанной очереди, 
хранящейся в таблице MySQL, или файлы, которые необходимо наблюдать и обрабатывать, 
как только актор увидит, что они появляются в определенном каталоге и так далее.
Этот продюсер должен передать работу нескольким переменным акторам.

Проблемы:

1. если очередь сообщений неограничена, 
   то при медленных потребителях эта очередь может взорваться
2. Распределение может быть неэффективным, 
   так как у работника может оказаться несколько ожидающих выполнения элементов, 
   в то время как другой работник может стоять на месте

Правильный и беспроблемный дизайн делает следующее:

- работники должны сигнализировать о потребности 
  (т.е. когда они готовы обрабатывать больше элементов)
- производитель должен производить продукцию только тогда, 
  когда есть спрос со стороны работников

Вот подробный образец с комментариями:

```scala
/**
 * Сообщение, означающее подтверждение того, 
 * что восходящий поток может отправить следующий элемент.
 */
case object Continue

/**
 * Сообщение, используемое производителем 
 * для непрерывного опроса источника данных в состоянии опроса.
 */
case object PollTick

/**
 * Машина состояний с 2 состояниями:
 *
 *  - Ожидание, что означает, что, вероятно, существует очередь элементов, 
 *    ожидающих отправки в нисходящий поток, но актор ожидает сигнала о требованию.
 *    
 *  - Опрос, что означает, что есть спрос со стороны нисходящего потока, 
 *    но актор ждет, пока что-то произойдет.
 *
 * ВАЖНО: в соответствии с протоколом этот актор не должен получать несколько событий Continue — 
 *        нижестоящий маршрутизатор должен дождаться доставки элемента, 
 *        прежде чем отправлять следующее событие Continue этому актору.
 */
class Producer(source: DataSource, router: ActorRef) extends Actor {
  import Producer.PollTick

  override def preStart(): Unit = {
    super.preStart()
    // это игнорирует еще одно правило, которое меня волнует
    // (акторы должны выделяться только в ответ на внешние сообщения), 
    // но мы оставим это в обучающих целях
    context.system.scheduler.schedule(1.second, 1.second, self, PollTick)
  }

  // актор начинает работу в режиме ожидания
  def receive = standby

  def standby: Receive = {
    case PollTick =>
      // игнорировать

    case Continue =>
      // сигнал о спросе, поэтому попробуйте отправить следующий элемент
      source.next() match {
        case None =>
          // нет доступных позиций, перейдите в режим опроса
          context.become(polling)
          
        case Some(item) =>
          // элемент доступен, отправьте его вниз по течению 
          // и оставайтесь в состоянии ожидания
          router ! item
      }
  }

  def polling: Receive = {
    case PollTick =>
      source.next() match {
        case None =>
          () // игнорировать - остается в опросе
        case Some(item) =>
          // элемент в наличии, спрос имеется
          router ! item
          // перейти в режим ожидания
          context.become(standby)
      }
  }
}

/**
 * Маршрутизатор является посредником между вышестоящим производителем и работниками, 
 * отслеживая спрос (чтобы упростить работу производителя).
 *
 * ПРИМЕЧАНИЕ. Необходимо соблюдать протокол производителя, 
 *       поэтому мы сигнализируем Continue вышестоящему производителю после 
 *       и только после того, как элемент был отправлен в нисходящий поток 
 *       для обработки работнику.
 */
class Router(producer: ActorRef) extends Actor {
  var upstreamQueue = Queue.empty[Item]
  var downstreamQueue = Queue.empty[ActorRef]

  override def preStart(): Unit = {
    super.preStart()
    // сигнализирует о первоначальном спросе на upstream
    producer ! Continue
  }

  def receive = {
    case Continue =>
      // сигнал о спросе поступает из нисходящего потока, 
      // если у нас есть элементы для отправки, то отправьте его, 
      // в противном случае поставьте в очередь нижестоящего потребителя
      if (upstreamQueue.isEmpty) {
        downstreamQueue = downstreamQueue.enqueue(sender)
      }
      else {
        // нет необходимости сигнализировать о спросе в восходящем направлении, 
        // поскольку у нас есть элементы в очереди, просто отправьте их в нисходящий поток
        val (item, newQueue) = upstreamQueue.dequeue
        upstreamQueue = newQueue
        sender ! item

        // сигнализировать о спросе на другой элемент
        producer ! Continue
      }

    case item: Item =>
      // элемент, сигнализируемый из восходящего потока, если у нас есть потребители в очереди, 
      // тогда сигнализируйте об этом в нисходящем направлении, 
      // в противном случае поставьте его в очередь
      if (downstreamQueue.isEmpty) {
        upstreamQueue = upstreamQueue.enqueue(item)
      }
      else {
        val (consumer, newQueue) = downstreamQueue.dequeue
        downstreamQueue = newQueue
        consumer ! item

        // сигнализировать о спросе на другой элемент
        producer ! Continue
      }
  }
}

class Worker(router: ActorRef) extends Actor {
  override def preStart(): Unit = {
    super.preStart()
    // сигнализирует о первоначальном спросе на upstream
    router ! Continue
  }
  
  def receive = {
    case item: Item =>
      process(item)
      router ! Continue
  }
}
```

### 5.5. НЕ СЛЕДУЕТ использовать Akka FSM

Akka представляет то, что многие считают крутым DSL для создания конечных автоматов, 
под названием [Akka FSM](http://doc.akka.io/docs/akka/current/scala/fsm.html). 

Но он неадекватен, ограничивает и его использование приводит к плохим практикам. 
Текущие проекты должны попытаться заменить его, а новые проекты должны его избегать.
Вместо этого предпочитайте моделировать конечные автоматы с помощью `context.become`. 

Три основные причины, по которым вам следует избегать Akka FSM: 

1. с Akka FSM вы можете моделировать только детерминированные конечные автоматы (DFA) 
2. Akka FSM вызывает нечистую, побочную логику в вашем акторе 
3. Akka FSM связывает вашу бизнес-логику с Akka, что затрудняет тестирование

Итак, чтобы объяснить это рассуждение. 
С помощью Akka FSM вы можете моделировать только детерминированные конечные автоматы, 
и в какой-то момент это приведет к неприятностям. 
Небольшой образец:

```scala
when (Available) {
  case Event(Setpoint(value), data) =>
    goto(Ramping) using data.copy(setpoint=value)
}

when (Ramping) {
  case Event(RampIsOver, data) =>
    goto(Dispatched)
}

onTransition {
  case Available -> Ramping =>
    logger.info("Ramping ...")
  case Ramping -> Dispatched =>
    logger.info("Dispatched ...")
}
```

Это DFA. Но большая проблема, которая возникнет, заключается в том, 
что для любого достаточно сложного конечного автомата 
вам понадобится инициировать несколько переходов в ответ на одно сообщение. 
Другими словами, в конечном итоге вам захочется сделать что-то вроде этого:

```scala
firstGoto(Ramping).thenGoto(Dispatched)
```

Это выдуманный API. Akka FSM это не поддерживает. 
И на этом этапе вам нужно будет форкнуть Akka FSM, чтобы получить его. 
Это, конечно, [выполнимо](https://gist.github.com/alexandru/b663d50642a25a3644f4277751c5adfb). 
Но если вы в конечном итоге используете Akka FSM для своего проекта, 
вы явно допустили ошибку, выбрав Akka FSM. 
И, конечно же, большинство людей никогда не подумают о форке Akka FSM, 
поэтому в конечном итоге им придется столкнуться с труднопроверяемыми и необоснованными хаками, 
такими как передача этому актору дополнительных внутренних сообщений.

Другая причина, по которой Akka FSM неадекватна, заключается в том, 
что он заставляет вас моделировать конечный автомат как вещь, которая изменяет свое состояние. 
По сути, в итоге вы получите следующее:

- объект с индивидуальностью и сложной бизнес-логикой (действительно, автоматы редко бывают простыми) 
- который взаимодействует с асинхронными сообщениями

Позвольте мне объяснить следующее: 
результат будет хуже, чем худший, который вы когда-либо видели при использовании ООП. 
Поскольку это будет объект, состояние которого зависит от его истории (например, от идентификатора), 
и поскольку связь является асинхронной, 
введение [nondeterminism](https://en.wikipedia.org/wiki/Nondeterministic_algorithm)
в эту логику действительно очень просто.

Это подводит меня к тому, что Akka FSM связывает вашу бизнес-логику с Akka, 
которая по определению является _комплементарной_. 
Это плохо, потому что: 

- вы не можете перенести эту бизнес-логику для использования другого решения
- чтобы протестировать вашу бизнес-логику, вам теперь нужно протестировать этого актора 
  и поддельные асинхронные коммуникации (с помощью 
  [akka-testkit](http://doc.akka.io/docs/akka/current/scala/testing.html)) 

Другими словами, вы в конечном итоге оказываетесь привязанными к Akka FSM 
и тестируете субъектов Akka вместо тестирования собственной бизнес-логики. 
И эти тесты, что бы ни говорили документы выше, ужасны. 
Потому что, как уже было сказано, 
соедините объекты с состоянием с асинхронными сообщениями, 
и вы получите нечто худшее, чем худшее, что вы когда-либо видели. 

**Решение** состоит в том, чтобы ваша бизнес-логика описывалась вне какого-либо актора 
и чтобы акторы отвечали только за коммуникации, 
желательно развивать с помощью `context.become`, как упоминалось в пункте `5.2`. 
В этот момент вам больше не нужно тестировать этих акторов, 
вам больше не нужно зависеть от `akka-testkit`. 
Но такая стратегия полностью исключит Akka FSM.

---

**Ссылки:**

- [Scala Best Practices от Alexandru Nedelcu][article]

[article]: https://github.com/alexandru/scala-best-practices
